package de.home.gpio.pinService;

import java.sql.SQLException;
import java.util.BitSet;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import com.pi4j.io.gpio.GpioPinDigitalOutput;
import com.pi4j.io.gpio.PinState;
import com.pi4j.wiringpi.Gpio;

import de.home.db.DBService;
import de.home.db.model.RemotePowerPoint;

public class PinSwitchService implements PinService {

   private static final Logger        LOG             = LogManager.getLogger(PinSwitchService.class);
   private static final int           PULSE_LENGTH    = 350;
   private static final int           REPEAT_TRANSMIT = 10;
   private DBService                  db;
   private final GpioPinDigitalOutput transmitterPin;

   public PinSwitchService(GpioPinDigitalOutput transmitterPin) {
      this.transmitterPin = transmitterPin;
      this.transmitterPin.setShutdownOptions(true, PinState.LOW);
      db = DBService.getInstance();
   }

   /**
    * Switch a remote switch on (Type A with 10 pole DIP switches)
    *
    * @param switchGroupAddress
    *           Code of the switch group (refers to DIP switches 1..5 where "1"
    *           = on and "0" = off, if all DIP switches are on it's "11111")
    * @param switchCode
    *           Number of the switch itself (1..4)
    */
   public void switchOn(BitSet switchGroupAddress, int switchCode) {
      if ( switchGroupAddress.length() != 5 ) {
         throw new IllegalArgumentException("The switchGroupAddress must consist of exactly 5 bits!");
      }
      if ( this.transmitterPin == null ) {
         LOG.error("GPIO port is null.");
         return;
      }
      this.sendTriState(this.getCodeWordA(switchGroupAddress, switchCode, true));
   }

   /**
    * Switch a remote switch off
    *
    * @param switchGroupAddress
    *           Code of the switch group (refers to DIP switches 1..5 where "1"
    *           = on and "0" = off, if all DIP switches are on it's "11111")
    * @param switchCode
    *           Number of the switch itself (1..4 for A..D)
    */
   public void switchOff(BitSet switchGroupAddress, int switchCode) {
      if ( switchGroupAddress.length() != 5 ) {
         throw new IllegalArgumentException("the switchGroupAddress must consist of exactly 5 bits!");
      }
      if ( this.transmitterPin == null ) {
         LOG.error("GPIO port is null.");
         return;
      }
      this.sendTriState(this.getCodeWordA(switchGroupAddress, switchCode, false));
   }

   /**
    * Like getCodeWord (Type A)
    */
   private String getCodeWordA(BitSet switchGroupAddress, int channelCode, boolean status) {
      int nReturnPos = 0;
      char[] sReturn = new char[12];

      String[] code = new String[] { "FFFFF", "0FFFF", "F0FFF", "FF0FF", "FFF0F", "FFFF0" };

      if ( channelCode < 1 || channelCode > 5 ) {
         return "";
      }

      for (int i = 0; i < 5; i++) {
         if ( !switchGroupAddress.get(i) ) {
            sReturn[nReturnPos++] = 'F';
         } else {
            sReturn[nReturnPos++] = '0';
         }
      }

      for (int i = 0; i < 5; i++) {
         sReturn[nReturnPos++] = code[channelCode].charAt(i);
      }

      if ( status ) {
         sReturn[nReturnPos++] = '0';
         sReturn[nReturnPos++] = 'F';
      } else {
         sReturn[nReturnPos++] = 'F';
         sReturn[nReturnPos++] = '0';
      }

      LOG.debug("Codeword NoName is: " + new String(sReturn));

      return new String(sReturn);
   }

   /**
    * Sends a Code Word
    *
    * @param codeWord
    *           /^[10FS]*$/ -> see getCodeWord
    */
   private void sendTriState(String codeWord) {
      for (int nRepeat = 0; nRepeat < REPEAT_TRANSMIT; nRepeat++) {
         for (int i = 0; i < codeWord.length(); ++i) {
            switch (codeWord.charAt(i)) {
               case '0':
                  this.sendT0();
                  break;
               case 'F':
                  this.sendTF();
                  break;
               case '1':
                  this.sendT1();
                  break;
            }
         }
         this.sendSync();
      }
   }

   /**
    * Sends a "Sync" Bit _ Waveform Protocol 1: |
    * |_______________________________ _ Waveform Protocol 2: | |__________
    */
   private void sendSync() {
      this.transmit(1, 31);
   }

   /**
    * Sends a Tri-State "0" Bit _ _ Waveform: | |___| |___
    */
   private void sendT0() {
      this.transmit(1, 3);
      this.transmit(1, 3);
   }

   /**
    * Sends a Tri-State "1" Bit ___ ___ Waveform: | |_| |_
    */
   private void sendT1() {
      this.transmit(3, 1);
      this.transmit(3, 1);
   }

   /**
    * Sends a Tri-State "F" Bit _ ___ Waveform: | |___| |_
    */
   private void sendTF() {
      this.transmit(1, 3);
      this.transmit(3, 1);
   }

   private void transmit(int nHighPulses, int nLowPulses) {
      this.transmitterPin.high();
      Gpio.delayMicroseconds(PinSwitchService.PULSE_LENGTH * nHighPulses);

      this.transmitterPin.low();
      Gpio.delayMicroseconds(PinSwitchService.PULSE_LENGTH * nLowPulses);
   }

   /**
    * convenient method to convert a string like "11011" to a BitSet.
    * 
    * @param address
    * @return a bitset containing the address that can be used for
    *         switchOn()/switchOff()
    */
   public static BitSet getSwitchGroupAddress(String address) {
      if ( address.length() != 5 ) {
         throw new IllegalArgumentException("the switchGroupAddress must consist of exactly 5 bits!");
      }
      BitSet bitSet = new BitSet(5);
      for (int i = 0; i < 5; i++) {
         bitSet.set(i, address.charAt(i) == '1');
      }
      return bitSet;
   }

   public void switchOn(String address, int switchCode) {
      try {
         switchOn(getSwitchGroupAddress(address), switchCode);
      } catch (Exception e) {
         LOG.error(e.getMessage(), e);
      }
   }

   public void switchOff(String address, int switchCode) {
      try {
         switchOff(getSwitchGroupAddress(address), switchCode);
      } catch (Exception e) {
         LOG.error(e.getMessage(), e);
      }
   }

   public void switchOn(int idInt) throws SQLException {
      RemotePowerPoint remotePowerPoint = db.getRemotePowerPoint(idInt);
      String hauscode = remotePowerPoint.getHauscode();
      int geraetenummer = remotePowerPoint.getGeraetenummer();

      if ( remotePowerPoint.getHersteller().equals("InterTechno") ) {
         String codeWord = hauscode + "FF";
         LOG.debug("CodeWord = '{}'", codeWord);
         this.sendTriState(codeWord);
      } else {
         switchOn(hauscode, geraetenummer);
      }

   }

   public void switchOff(int idInt) throws SQLException {
      RemotePowerPoint remotePowerPoint = db.getRemotePowerPoint(idInt);
      String hauscode = remotePowerPoint.getHauscode();
      int geraetenummer = remotePowerPoint.getGeraetenummer();

      if ( remotePowerPoint.getHersteller().equals("InterTechno") ) {
         String codeWord = hauscode + "F0";
         LOG.debug("CodeWord = '{}'", codeWord);
         this.sendTriState(codeWord);
      } else {
         switchOff(hauscode, geraetenummer);
      }

   }

}
